package lvq;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class Rede {

	private Neuronio[][] neuronios;
	private int largura;
	private int altura;

	private final double SIGMA_INIIAL = 1.8;
	private final double CONSTANTE_DE_TEMPO = 30;
	
	public Rede(int quantidadeAtributos, int largura, int altura) {

		this.largura = largura;
		this.altura = altura;
		neuronios = new Neuronio[largura][altura];
		inicializaNeuronios(quantidadeAtributos, largura, altura);
	}

	private void inicializaNeuronios(int quantidadeAtributos, int largura, int altura) {

		double x = -largura / 2;
		double y = -altura / 2;

		for (int i = 0; i < largura; i++) {
			for (int j = 0; j < altura; j++) {

				double[] coordenadas = new double[] { x, y };
				neuronios[i][j] = new Neuronio(coordenadas, i, j);
				x++;
			}
			y++;
			x = -largura / 2;
		}
	}

	@SuppressWarnings("unused")
	private void inicializaNeuroniosAleatoriamente(int quantidadeAtributos, int largura, int altura) {

		for (int i = 0; i < largura; i++) {
			for (int j = 0; j < altura; j++) {

				neuronios[i][j] = new Neuronio(quantidadeAtributos, i, j);
			}
		}
	}

	public void treinaRede(double[] coordenadasDado, double taxaAprendizagem, int tempo) {

		Neuronio bmu = neuronioMaisProximo(coordenadasDado);
		aproximaBmuEVizinhos(taxaAprendizagem, bmu, tempo, coordenadasDado);
		
	}

	private void aproximaBmuEVizinhos(double taxaAprendizagem, Neuronio bmu, int tempo, double[] coordenadasDados) {

		for (int i = 0; i < largura; i++) {

			for (int j = 0; j < altura; j++) {

				int raioDoNeuronioAtual = calculaRaioDoVizinho(bmu, i, j);
				neuronios[i][j].aproximaNeuronio(taxaAprendizagem, gaussiana(raioDoNeuronioAtual, tempo), coordenadasDados);

			}
		}


	}

	@SuppressWarnings("unused")
	private double bubble(int raioDoVizinho, int tempo) {

		int raioDeVizinhancaInicial = 7;
		
		int raioDeVizinhancaAtual = 1;
		
		if(raioDeVizinhancaInicial - tempo > 1){
			raioDeVizinhancaAtual = raioDeVizinhancaInicial - tempo;
		}
		
		
		if (raioDoVizinho <= raioDeVizinhancaAtual){
			return 1;
		}
		return 0;
	}

	private int calculaRaioDoVizinho(Neuronio neuronio, int i, int j) {

		int distanciaHorizontal = Math.abs(neuronio.x() - i);
		int distanciaVertical = Math.abs(neuronio.y() - j);

		return max(distanciaHorizontal, distanciaVertical);
	}

	private int max(int distanciaHorizontal, int distanciaVertical) {

		if (distanciaHorizontal > distanciaVertical) {
			return distanciaHorizontal;
		} else {
			return distanciaVertical;
		}

	}

	private Neuronio neuronioMaisProximo(double[] coordenadasDado) {

		List<Neuronio> neuroniosMaisProximos = new ArrayList<Neuronio>();
		
		double menorDistancia = Double.MAX_VALUE;
		double distancia = -1;

		for (int i = 0; i < largura; i++) {

			for (int j = 0; j < altura; j++) {

				distancia = neuronios[i][j].distanciaEuclidiana(coordenadasDado);

				if (distancia == menorDistancia) {
					neuroniosMaisProximos.add(neuronios[i][j]);
				}
				
				else if (distancia < menorDistancia) {
					
					neuroniosMaisProximos = new ArrayList<Neuronio>();
					neuroniosMaisProximos.add(neuronios[i][j]);
					menorDistancia = distancia;
					
				}
			}
		}
		Collections.shuffle(neuroniosMaisProximos);
	
		return neuroniosMaisProximos.get(0);
	}

	private double gaussiana(double distancia, int tempo) {
		return Math.pow(Math.E, (- distancia) / 2 * sigmaAoQuadrado(tempo));
	}

	private double sigmaAoQuadrado(int tempo) {
		
		double sigma = SIGMA_INIIAL * Math.pow(Math.E, -tempo/ CONSTANTE_DE_TEMPO);
		return sigma * sigma;
	}

	public Neuronio[][] neuronios() {
		return neuronios;
	}

	public int largura() {
		return largura;
	}

	public int altura() {
		return altura;
	}

	public void imprime() {

		for (int i = 0; i < largura; i++) {
			for (int j = 0; j < altura; j++) {
				neuronios[i][j].imprime();
			}
			System.out.println();
		}
	}
}
